// for database lineage;
define(["d3", "joint", "jQuery", "core"], function (d3, jointFactory, jQuery, core) {
    'use strict';

    var baseUrl = "/console/analysis/lineage";

    function Lineage() {
        var joint = jointFactory.create();
        var svg, tooltip, path, defs, colorScale, highlightColorScale, isTransitioning;
        var nodes, links, selector, gNodes, gLinks, nodeEvents = [];

        var d3Link, d3LinkEnter, d3Node, d3NodeEnter;//update var

        var OPACITY = {
                NODE_DEFAULT: 0.9,
                NODE_FADED: 0.5,
                NODE_HIGHLIGHT: 0.8,
                LINK_DEFAULT: 0.6,
                LINK_FADED: 0.05,
                LINK_HIGHLIGHT: 0.9
            },
            TYPES = ["database", "table", "column"],
            TYPE_COLORS = ["#ffffff", "#0066FF", "#009FCC", "#77DDFF", "#BBFFEE", "#D3D3D3", "#C0C0C0", "#A9A9A9"],
        // TYPE_COLORS = ["none"],

            TYPE_HIGHLIGHT_COLORS = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f", "#e5c494"],
            LINK_COLOR = "#b3b3b3",
            INFLOW_COLOR = "#2E86D1",
            OUTFLOW_COLOR = "#D63028",
            ICON_COLOR = "#000000",
            NODE_WIDTH = 36,
            OUTER_MARGIN = 10,
            MARGIN = {
                TOP: OUTER_MARGIN,
                RIGHT: OUTER_MARGIN,
                BOTTOM: OUTER_MARGIN,
                LEFT: OUTER_MARGIN
            },
            TRANSITION_DURATION = 400,
            HEIGHT = ($(window).height() - 305 - MARGIN.TOP - MARGIN.BOTTOM) / 1,//full
        //HEIGHT = 500 - MARGIN.TOP - MARGIN.BOTTOM,
            WIDTH = ($(window).width() - 270 - MARGIN.LEFT - MARGIN.RIGHT) / 1,//full
        // WIDTH = 960 - MARGIN.LEFT - MARGIN.RIGHT,
            LAYOUT_INTERATION = 32,
            REFRESH_INTERVAL = 7000,
            TEXT_LAYOUT_TYPE = {
                TOP: "top",
                BOTTOM: "bottom",
                ENTER: "enter",
                SIDE: "side",
                RIGHT: "right"
            }

        var options = {
            svg: svg,
            tooltip: tooltip,
            path: path,
            defs: defs,
            colorScale: colorScale,
            highlightColorScale: highlightColorScale,
            // isTransitioning: isTransitioning,
            opacity: OPACITY,
            types: TYPES,
            typeColor: TYPE_COLORS,
            typeHighlightColor: TYPE_HIGHLIGHT_COLORS,
            linkColor: LINK_COLOR,
            inflowColor: INFLOW_COLOR,
            outflowColor: OUTFLOW_COLOR,
            iconColor: ICON_COLOR,
            nodeWith: NODE_WIDTH,
            nodeMinHeight: 20,
            outerMargin: OUTER_MARGIN,
            margin: MARGIN,
            transitionDuration: TRANSITION_DURATION,
            height: HEIGHT,
            width: WIDTH,
            layoutInteration: LAYOUT_INTERATION,
            refreshInterval: REFRESH_INTERVAL,
            textLayout: TEXT_LAYOUT_TYPE.SIDE,
            offsetLeftToRightX: 0,
            offsetRightToLeftX: 0,
        };

        this.setOptions = function (_) {
            options = $.extend({}, options, _);
            return this
        }

        var formatNumber = function (d) {
            var numberFormat = d3.format(",.0f"); // zero decimal places
            return "£" + numberFormat(d);
        };

        var formatFlow = function (d) {
            var flowFormat = d3.format(",.0f"); // zero decimal places with sign
            return "£" + flowFormat(Math.abs(d)) + (d < 0 ? " CR" : " DR");
        };

        // Used when temporarily disabling user interractions to allow animations to complete
        var disableUserInteractions = function (time) {
            isTransitioning = true;
            setTimeout(function () {
                isTransitioning = false;
            }, time);
        };

        function findTableAndColumnNodes(tableId, aNodes) {
            return aNodes.filter(function (d) {
                if (d.id == tableId || d.parent == tableId)    return true;
                else return false;
            })
        }

        function findRootNodes(nodes) {
            var result = [];
            nodes.forEach(function (node) {
                if (!hasParent(node)) result.push(node);
            })

            return result;
        }

        function hasParent(node) {
            if (node.parent != undefined && node.parent != null) return true;
            else return false;
        }

        function findDatabaseNodes(nodes) {
            var result = [];
            nodes.forEach(function (node) {
                if (isDatabaseNode(node)) result.push(node);
            });

            return result;
        }

        function isColumnNode(node) {
            if (node.type == "column") return true;
            else return false;
        }

        function isNotColumnNode(node) {
            return !isColumnNode(node);
        }

        function isTableNode(node) {
            if (node.type == "table") return true;
            else return false;
        }

        function isNotTableNode(node) {
            return !isTableNode(node);
        }

        function isDatabaseNode(node) {
            if (node.type == "database") return true;
            else return false;
        }

        function isNotDatabaseNode(node) {
            return !isDatabaseNode(node);
        }

        function findCascadeSourceLinks(g) {
            var map = d3.map();

            function r(d) {
                d.sourceLinks.forEach(function (link) {
                    if (!map.has(link.id)) {
                        map.set(link.id, link);
                        r(link.target);
                    }
                });
            }

            r(g);

            return map;
        }

        function findCascadeTargetLinks(g) {
            var map = d3.map();

            function r(d) {
                d.targetLinks.forEach(function (link) {
                    if (!map.has(link.id)) {
                        map.set(link.id, link);
                        r(link.source);
                    }
                });
            }

            r(g);

            return map;
        }

        function containChildren(node) {
            node.children.forEach(function (child) {
                child.state = "contained";
                child.parent = this;
                child._parent = null;
                containChildren(child);
            }, node);
        }

        function expand(node) {
            node.state = "expanded";
            node.children.forEach(function (child) {
                child.state = "collapsed";
                child._parent = this;
                child.parent = null;
                containChildren(child);
            }, node);
        }

        function collapse(node) {
            node.state = "collapsed";
            containChildren(node);
        }

        function tidyParentAndChildrenSameX(parent, children) {
            if (!children) return;
            children.forEach(function (child) {
                child.x = parent.x;
                tidyParentAndChildrenSameX(child, child.children);
            })
        }

        function tidyParentAndChildrenSeriesY(parent, children) {
            if (!children) return;
            var value = options.nodeMinHeight;
            children.forEach(function (child) {
                child.y = parent.y + value;
                tidyParentAndChildrenSeriesY(child, child.children);
                value += child.height;
            })
        }

        function childrenFollow(node) {
            tidyParentAndChildrenSameX(node, node.children);
            tidyParentAndChildrenSeriesY(node, node.children);
        }

        function showHideChildren(node) {
            disableUserInteractions(2 * TRANSITION_DURATION);
            hideTooltip();
            if (node.state === "collapsed") {
                expand(node);
            }
            else {
                collapse(node);
            }

            childrenFollow(node);
            joint.relayout();

            update();

            d3Link.attr("d", path);
            restoreLinksAndNodes(d3Node, d3Link, options);
        }

        function restoreLinksAndNodes(node, link, options) {
            link.classed("link-animation-database", function () {
                    return false;
                })
                .style("stroke", options.linkColor)
                .style("marker-end", function () {
                    return 'url(#arrowHead)';
                })
                .transition()
                .duration(options.transitionDuration)
                .style("opacity", options.opacity.LINK_DEFAULT);

            node
                .selectAll("rect")
                .style("fill", function (d) {
                    d.color = options.colorScale(d.type.replace(/ .*/, ""));
                    return d.color;
                })
                .style("stroke", function (d) {
                    return d3.rgb(options.colorScale(d.type.replace(/ .*/, ""))).darker(0.1);
                })
                .style("fill-opacity", options.opacity.NODE_DEFAULT);

            node.filter(function (n) {
                    return n.state === "collapsed" || !hasParent(n);
                })
                .transition()
                .duration(options.transitionDuration)
                .style("opacity", options.opacity.NODE_DEFAULT);
        }

        function highlightConnected(g, node, link, options) {
            var sourceLinks = findCascadeSourceLinks(g);
            var targetLinks = findCascadeTargetLinks(g);
            link.filter(function (d) {
                    // return d.source === g;
                    return sourceLinks.has(d.id);
                })
                .classed("link-animation-database", function () {
                    return true;
                })
                .style("marker-end", function () {
                    return 'url(#arrowHeadInflow)';
                })
                .style("stroke", options.outflowColor)
                .style("opacity", options.opacity.LINK_DEFAULT);

            link.filter(function (d) {
                    // return d.target === g;
                    return targetLinks.has(d.id);
                })
                .classed("link-animation-database", function () {
                    return true;
                })
                .style("marker-end", function () {
                    return 'url(#arrowHeadOutlow)';
                })
                .style("stroke", options.inflowColor)
                .style("opacity", options.opacity.LINK_DEFAULT);
        }

        function fadeUnconnected(g, node, link, options) {
            var sourceLinks = findCascadeSourceLinks(g);
            var targetLinks = findCascadeTargetLinks(g);
            link.filter(function (d) {
                    return !sourceLinks.has(d.id) && !targetLinks.has(d.id);
                })
                .style("marker-end", function () {
                    return 'url(#arrowHead)';
                })
                .transition()
                .duration(options.transitionDuration)
                .style("opacity", options.opacity.LINK_FADED);

            node.filter(function (d) {
                // sourceLinks.forEach(function (link) {
                //     if (link.source.name === d.name || link.target.name === d.name) return false;
                // })
                //
                // targetLinks.forEach(function (link) {
                //     if (link.source.name === d.name) return false;
                //     if (link.target.name === d.name) return true;
                // })

                return (d.name === g.name) ? false : !joint.connected(d, g);
            }).transition()
                .duration(options.transitionDuration)
                .style("opacity", options.opacity.NODE_FADED);
        }

        var hideTooltip = function () {
            return tooltip.transition()
                .duration(TRANSITION_DURATION)
                .style("opacity", 0);
        };

        var showTooltip = function () {
            return tooltip
                .style("left", d3.event.pageX + "px")
                .style("top", d3.event.pageY - 200 + 15 + "px")
                .transition()
                .duration(TRANSITION_DURATION)
                .style("opacity", 1);
        };

        function getNodeTooltipHtml(d) {
            if (d.type == "database") return prepareDatabaseHtml(d);
            // else if (d.type == "table") return prepareTableHtml(d);
            // else if (d.type == "column") return prepareCommonHtml(d);
            else return "";
        }

        function prepareCommonHtml(d) {
            var html = ""
                + "<div><table class='table table-bordered table-striped table-hover'>" +
                "<thead><th>名称</th><th>内容</th></thead>";
            if (d.data) {
                html += "<tbody>";
                for (var i in d.data) {
                    html += "<tr><td>" + i
                        + "</td><td>" + (d.data[i])
                        + "</td></tr>";
                }
                html += "</tbody>";
            }

            html += "</table></div>"
            return html;
        }

        function prepareDatabaseHtml(d) {
            var html = ""
                + "<div><table class='table table-bordered table-striped table-hover'>" +
                "<thead><th style='width: 40%'>名称</th><th style='width: 60%'>内容</th></thead>";

            function addTr(name, value) {
                html += "<tr><td>" + name
                    + "</td><td>" + value
                    + "</td></tr>";
            }

            html += "<tbody>";

            addTr("当前各表的数据量统计", d.dataCount);
            addTr("自身现在拥有表的数量", d.ownTableSize);
            addTr("作为来源关联表的数量", d.relSourceTableSize);
            addTr("作为目标关联表的数量", d.relTargetTableSize);

            if (d.data) {
                for (var i in d.data) {
                    addTr(i, d.data[i]);
                }
            }

            html += "</tbody>";

            html += "</table></div>"
            return html;
        }

        function prepareTableHtml(d) {
            var html = prepareCommonHtml(d);
            html += "<div class='box-head'>字段信息</div>";
            html += "<div class='box-body'><table class='table table-bordered'><thead><th>名称</th><td>类型</td><th>长度</th></thead>";
            if (d.children) {
                d.children.forEach(function (child) {
                    html += "<tr><td>" + child.name
                        + "</td><td>" + child.data.type
                        + "</td><td>" + child.data.size
                        + "</td></tr>";
                })
            }

            html += "</table></div>";
            return html;
        }

        function update() {
            function updateLink(link) {
                link.transition()
                    .duration(TRANSITION_DURATION)
                    .style("stroke-WIDTH", function (d) {
                        return Math.max(1, d.thickness);
                    })
                    .attr("d", path)
                    .style("opacity", OPACITY.LINK_DEFAULT);

                link.exit().remove();

                var linkEnter = link.enter().append("path")
                    .attr("class", "link")
                    .style("fill", "none");

                // linkEnter.on('mouseenter', function (d) {
                //     if (!isTransitioning) {
                //         showTooltip().select(".value").text(function () {
                //             if (d.direction > 0) {
                //                 return d.source.name + " → " + d.target.name + "\n" + formatNumber(d.value);
                //             }
                //             return d.target.name + " ← " + d.source.name + "\n" + formatNumber(d.value);
                //         });
                //
                //         d3.select(this)
                //             .style("stroke", LINK_COLOR)
                //             .transition()
                //             .duration(TRANSITION_DURATION / 2)
                //             .style("opacity", OPACITY.LINK_HIGHLIGHT);
                //     }
                // });
                //
                // linkEnter.on('mouseleave', function () {
                //     if (!isTransitioning) {
                //         hideTooltip();
                //
                //         d3.select(this)
                //             .style("stroke", LINK_COLOR)
                //             .transition()
                //             .duration(TRANSITION_DURATION / 2)
                //             .style("opacity", OPACITY.LINK_DEFAULT);
                //     }
                // });

                linkEnter.sort(function (a, b) {
                        return b.thickness - a.thickness;
                    })
                    .classed("leftToRight", function (d) {
                        return d.direction > 0;
                    })
                    .classed("rightToLeft", function (d) {
                        return d.direction < 0;
                    })
                    .style("marker-end", function () {
                        return 'url(#arrowHead)';
                    })
                    .style("stroke", LINK_COLOR)
                    .style("opacity", 0)
                    .transition()
                    .delay(TRANSITION_DURATION)
                    .duration(TRANSITION_DURATION)
                    .attr("d", path)
                    .style("stroke-WIDTH", function (d) {
                        return Math.max(1, d.thickness);
                    })
                    .style("opacity", OPACITY.LINK_DEFAULT);
            }

            function updateNode(node) {
                node.transition()
                    .duration(TRANSITION_DURATION)
                    .attr("transform", function (d) {
                        return "translate(" + d.x + "," + d.y + ")";
                    })
                    .style("opacity", OPACITY.NODE_DEFAULT)
                    .select("rect")
                    .style("fill", function (d) {
                        d.color = colorScale(d.type.replace(/ .*/, ""));
                        return d.color;
                    })
                    .style("stroke", function (d) {
                        return d3.rgb(colorScale(d.type.replace(/ .*/, ""))).darker(0.1);
                    })
                    .style("stroke-WIDTH", function (d) {
                        return "1px";
                    })
                    .attr("height", function (d) {
                        return d.height;
                    })
                    .attr("width", function (d) {
                        return joint.nodeWidth();
                    });

                node.exit().remove();
            }

            function addNodeInnerElement(nodeEnter) {
                nodeEnter
                    .attr("transform", function (d) {
                        // var startX = d._parent ? d._parent.x : d.x,
                        //     startY = d._parent ? d._parent.y : d.y;
                        // console.log(d.id + " translate(" + startX + "," + startY + ")")
                        // return "translate(" + startX + "," + startY + ")";
                        // transition 有问题，所以不能从父节点动画移动到准确位置，只能直接出现。
                        return "translate(" + d.x + "," + d.y + ")";
                    })
                    .style("opacity", 1e-6)
                    .transition()
                    .duration(TRANSITION_DURATION)
                    .style("opacity", options.opacity.NODE_DEFAULT)
                    .attr("transform", function (d) {
                        return "translate(" + d.x + "," + d.y + ")";
                    });

                nodeEnter.append("use");

                nodeEnter.append("rect")
                    .style("fill", function (d) {
                        d.color = colorScale(d.type.replace(/ .*/, ""));
                        return d.color;
                    })
                    .style("stroke", function (d) {
                        return d3.rgb(colorScale(d.type.replace(/ .*/, ""))).darker(0.1);
                    })
                    .style("stroke-WIDTH", "0px")
                    .attr("height", function (d) {
                        return d.height;
                    })
                    .attr("width", joint.nodeWidth())
                    .style("opacity", 0);

                nodeEnter.append("text");

            }

            function showDatabaseTooltip(g) {
                // var x = 10, y = 50;
                var x = d3.event.layerX, y = d3.event.layerY;
                // if(d3.event.layerX > WIDTH / 2)  x = 0 ;
                // else x = d3.event.layerX - 200;
                // if(d3.event.layerY > HEIGHT / 2)  y = 0 ;
                // else y = d3.event.layerY - 200;

                document.querySelector("#tooltipDb .value").innerHTML = getNodeTooltipHtml(g);
                tooltip
                    .style("left", x + "px")
                    .style("top", y + "px")
                    .transition()
                    .duration(TRANSITION_DURATION)
                    .style("opacity", 1).select(".value")
            }

            function addNodeLinkEvents(node) {
                node.on("mouseenter", function (g) {
                    if (!isTransitioning) {
                        restoreLinksAndNodes(node, d3Link, options);
                        highlightConnected(g, node, d3Link, options);
                        fadeUnconnected(g, node, d3Link, options);

                        d3.select(this).select("rect")
                            .style("fill", function (d) {
                                d.color = d.netFlow > 0 ? INFLOW_COLOR : OUTFLOW_COLOR;
                                return d.color;
                            })
                            .style("stroke", function (d) {
                                return d3.rgb(d.color).darker(0.1);
                            })
                            .style("fill-opacity", OPACITY.LINK_DEFAULT);

                        showDatabaseTooltip(g);
                    }
                });

                node.on("mouseleave", function () {
                    if (!isTransitioning) {
                        hideTooltip();
                        restoreLinksAndNodes(node, d3Link, options);
                    }
                });

                // node.filter(function (d) {
                //         return d.children.length;
                //     })
                //     .on("dblclick", function (n) {
                //         isNotDatabaseNode(n) && showHideChildren(n);
                //     });

                // allow nodes to be dragged to new positions
                node.call(d3.behavior.drag()
                    .origin(function (d) {
                        return d;
                    })
                    .on("dragstart", function () {
                        this.parentNode.appendChild(this);
                    })
                    .on("drag", dragMove))

                nodeEvents.forEach(function (event) {
                    node.on(event.name, event.callback);
                    d3NodeEnter.on(event.name, event.callback);
                })
            }

            function updateNodeContent(node) {
                // add in the text for the nodes
                switch (options.textLayout) {
                    case TEXT_LAYOUT_TYPE.SIDE:
                        node
                            .select("text")
                            .attr("x", -6)
                            .attr("y", function (d) {
                                return d.height / 2;
                            })
                            .attr("dy", ".35em")
                            .attr("text-anchor", "end")
                            .attr("transform", null)
                            .text(function (d) {
                                return d.name;
                            })
                            .filter(function (d) {
                                return d.x < WIDTH / 2;
                            })
                            .attr("x", 6 + joint.nodeWidth())
                            .attr("text-anchor", "start");
                        break;
                    case TEXT_LAYOUT_TYPE.ENTER:
                        node
                            .select("text")
                            .attr("x", 6)
                            .attr("y", function (d) {
                                return d.height / 2;
                            })
                            .attr("dy", ".35em")
                            .attr("text-anchor", "end")
                            .attr("transform", null)
                            .text(function (d) {
                                return d.name;
                            })
                            .attr("text-anchor", "start");
                        break;
                    case TEXT_LAYOUT_TYPE.RIGHT:
                        node
                            .select("text")
                            .attr("x", 6 + joint.nodeWidth())
                            .attr("y", function (d) {
                                return d.height / 2;
                            })
                            .attr("dy", ".35em")
                            .attr("text-anchor", "start")
                            .attr("transform", null)
                            .text(function (d) {
                                return d.name;
                            })
                            .filter(function (d) {
                                return d.x < WIDTH / 2;
                            })
                            .attr("x", 6 + joint.nodeWidth())
                            .attr("text-anchor", "start");
                        break;
                }

                node.select("use")
                    .attr("xlink:href", function (d) {
                        return "#" + d.type;
                    })
                    .attr("x", function (d) {
                        return -12;
                    })
                    .attr("y", function (d) {
                        return -12;
                    })
                    .attr("transform", "translate(6,6),scale(0.95)");
            }

            function dragMove(node) {
                if (node.type == "database" || node.type == "table") {
                    var state = node.state;
                    if (node.state !== "collapsed") {
                        showHideChildren(node);
                    }
                    node.x = Math.max(0, Math.min(WIDTH - node.width, d3.event.x));
                    node.y = Math.max(0, Math.min(HEIGHT - node.height, d3.event.y));
                    d3.select(this).attr("transform", "translate(" + node.x + "," + node.y + ")");

                    childrenFollow(node);
                    joint.relayout();
                    svg.selectAll(".node").selectAll("rect").attr("height", function (d) {
                        return d.height;
                    });
                    d3Link.attr("d", path);

                    update();
                }
            }

            d3Link = gLinks.selectAll("path.link")
                .data(joint.visibleLinks(), function (d) {
                    return d.id;
                });

            updateLink(d3Link);

            var displayNodes = findRootNodes(nodes);
            displayNodes.concat(joint.collapsedNodes());
            d3Node = gNodes.selectAll(".node")
                .data(displayNodes, function (d) {
                    return d.id;
                });
            updateNode(d3Node);

            d3NodeEnter = d3Node.enter().append("g").attr("class", "node");
            addNodeInnerElement(d3NodeEnter);
            addNodeLinkEvents(d3Node, d3Link);
            updateNodeContent(d3Node);

            // svg.attr("transform", "scale(1.0)");
            childrenFollow(d3Node);
        }


        function initDefs() {
            defs = svg.append("defs");

            defs.append("marker")
                .style("fill", LINK_COLOR)
                .attr("id", "arrowHead")
                .attr("viewBox", "0 0 6 10")
                .attr("refX", "1")
                .attr("refY", "5")
                .attr("markerUnits", "strokeWidth")
                .attr("markerWidth", "1")
                .attr("markerHeight", "1")
                .attr("orient", "auto")
                .append("path")
                .attr("d", "M 0 0 L 1 0 L 6 5 L 1 10 L 0 10 z");

            defs.append("marker")
                .style("fill", OUTFLOW_COLOR)
                .attr("id", "arrowHeadInflow")
                .attr("viewBox", "0 0 6 10")
                .attr("refX", "1")
                .attr("refY", "5")
                .attr("markerUnits", "strokeWidth")
                .attr("markerWidth", "1")
                .attr("markerHeight", "1")
                .attr("orient", "auto")
                .append("path")
                .attr("d", "M 0 0 L 1 0 L 6 5 L 1 10 L 0 10 z");

            defs.append("marker")
                .style("fill", INFLOW_COLOR)
                .attr("id", "arrowHeadOutlow")
                .attr("viewBox", "0 0 6 10")
                .attr("refX", "1")
                .attr("refY", "5")
                .attr("markerUnits", "strokeWidth")
                .attr("markerWidth", "1")
                .attr("markerHeight", "1")
                .attr("orient", "auto")
                .append("path")
                .attr("d", "M 0 0 L 1 0 L 6 5 L 1 10 L 0 10 z");
        }

        function initTooltip() {
            tooltip = d3.select(selector).append("div").attr("id", "tooltipDb");

            var html = '<div class="box box-info box-solid" style="width: 40rem">' +
                '<div class="box-header with-border">' +
                '<h3 class="box-title">数据库节点信息</h3>' +
                '</div><!-- /.box-header -->' +
                '<div class="box-body no-padding value">' +
                '</div><!-- /.box-body -->' +
                '</div>';
            document.querySelector("#tooltipDb").innerHTML = html;
        }

        function initJointAndPath() {
            // Set the joint diagram properties
            joint
                .nodeWidth(options.nodeWith)
                .nodeMinHeight(options.nodeMinHeight)
                .nodeSpacing(10)
                .linkSpacing(4)
                .arrowheadScaleFactor(0.5) // Specifies that 0.5 of the link's stroke WIDTH should be allowed for the marker at the end of the link.
                .size([WIDTH, HEIGHT]);

            joint.setOptions({
                link: {
                    maxThickness: 30,
                    offsetLeftToRightX: options.offsetLeftToRightX,
                    offsetRightToLeftX: options.offsetRightToLeftX
                },
                node: {
                    maxHeight: 100
                }
            });

            path = joint.link().curvature(0.45);

            joint
                .nodes(nodes)
                .links(links)
                .initializeNodes(function (node) {
                    node.state = node.parent ? "contained" : "collapsed";
                })
                .layout(LAYOUT_INTERATION);
        }

        function initBase(aSelector, aNodes, aLinks) {
            selector = aSelector;
            nodes = aNodes;
            links = aLinks;
            colorScale = d3.scale.ordinal().domain(TYPES).range(TYPE_COLORS);
            options.colorScale = colorScale;
            // highlightColorScale = d3.scale.ordinal().domain(TYPES).range(TYPE_HIGHLIGHT_COLORS);
        }

        function initSvgAndG() {
            document.querySelector(selector).innerHTML = "";
            svg = d3.select(selector).append("svg")
                .attr("width", WIDTH + MARGIN.LEFT + MARGIN.RIGHT)
                .attr("height", HEIGHT + MARGIN.TOP + MARGIN.BOTTOM)
                .append("g")
                .attr("transform", "translate(" + MARGIN.LEFT + "," + MARGIN.TOP + ")");


            gLinks = svg.append("g").attr("class", "links");
            gNodes = svg.append("g").attr("class", "nodes");
        }

        function prepareDatabaseTableListHtml(g) {
            var html =
                '<div class="box-body no-padding">' +
                '<table class="table table-bordered table-striped table-hover">' +
                '<thead>' +
                '<th style="font-weight: bold">表名</th>' +
                '<th style="font-weight: bold">作为来源关联字段的数量</th>' +
                '<th style="font-weight: bold">作为目标关联字段的数量</th>' +
                '<th></th></thead><tbody>';
            g.nodes.filter(function (d) {
                return d.relSourceColumnSize != 0 || d.relTargetColumnSize != 0;
            }).forEach(function (d) {
                html += '<tr><td style="font-weight: bold">' + d.name + '</td>' +
                    '<td>' + d.relSourceColumnSize + '</td>' +
                    '<td>' + d.relTargetColumnSize + '</td><td>' +
                    '<div class="btn-group no-padding">' +
                    '<button class="btn btn-default btn-sm look-table-lineage"' +
                    'data-table-id="' + d.dbId + '" ' +
                    'data-group-id="' + d.groupId + '"' +
                    'data-group-type="database">' +
                    '<i class="fa fa-search">查看</i>' +
                    '</button></div>'
                "</td></tr>";
            });
            html += '</tbody></table></div>';
            return html;
        }

        function prepareDvTableListHtml(g) {
            var html =
                '<div class="box-body no-padding">' +
                '<table class="table table-bordered table-striped table-hover">' +
                '<thead>' +
                '<th style="font-weight: bold">表名</th>' +
                '<th></th></thead><tbody>';
            g.nodes.forEach(function (d) {
                html += '<tr><td style="font-weight: bold">' + d.name + '</td><td>' +
                    '<div class="btn-group no-padding">' +
                    '<button class="btn btn-default btn-sm look-table-lineage"' +
                    'data-table-id="' + d.dbId + '"' +
                    'data-group-id="' + d.groupId + '" ' +
                    'data-group-type="dv">' +
                    '<i class="fa fa-search">查看</i>' +
                    '</button></div>'
                "</td></tr>";
            });
            html += '</tbody></table></div>';
            return html;
        }

        function ajaxTableLineageList(g) {
            console.log(g);
            var url = "";
            if (g.groupType == "database") {
                url = baseUrl + "/rest/findDatabaseTableList/" + g.groupId
                    + "/" + g.nodeId + "/" + g.dbConfigId;
                $.ajax({
                    url: core.getUrl(url),
                    dataType: "json",
                    success: function (d) {
                        $("#lineageTableListModalBody").html(prepareDatabaseTableListHtml(d));
                        $("#lineageTableListModalBody").addClass("no-padding");
                        $("#lineageTableListModalHeader").addClass("with-border");

                        $(".look-table-lineage").on("click", ajaxTableLineage);
                        $("#lineageTableListModal").modal();
                    }
                })
            } else if (g.groupType == "dv") {
                url = baseUrl + "/rest/findDvTableList/" + g.groupId
                    + "/" + g.modelType;
                $.ajax({
                    url: core.getUrl(url),
                    dataType: "json",
                    success: function (d) {
                        $("#lineageTableListModalBody").html(prepareDvTableListHtml(d));
                        $("#lineageTableListModalBody").addClass("no-padding");
                        $("#lineageTableListModalHeader").addClass("with-border");

                        $(".look-table-lineage").on("click", ajaxTableLineage);
                        $("#lineageTableListModal").modal();
                    }
                })
            }
        }

        function ajaxTableLineage(event) {
            var groupType = $(this).data("group-type");
            if(groupType == "database"){
                var groupId = $(this).data("group-id");
                var tableId = $(this).data("table-id");

                $.ajax({
                    url: core.getUrl(baseUrl + "/databaseTableLineage/" + groupId + "/" + tableId),
                    dataType: "html",
                    success: function (d) {
                        $("#tableLineageBox").html(d);
                        $("#databaseLineage").addClass("hide");
                        $("#tableLineageBox").removeClass("hide");
                        $("#lineageTableListModal").modal('hide');
                    }
                })
            } else if(groupType == "dv") {
                var groupId = $(this).data("group-id");
                var tableId = $(this).data("table-id");
                console.log(core.getUrl(baseUrl + "/dvTableLineage/" + groupId + "/" + tableId));
                $.ajax({
                    url: core.getUrl(baseUrl + "/dvTableLineage/" + groupId + "/" + tableId),
                    dataType: "html",
                    success: function (d) {
                        $("#tableLineageBox").html(d);
                        $("#databaseLineage").addClass("hide");
                        $("#tableLineageBox").removeClass("hide");
                        $("#lineageTableListModal").modal('hide');
                    }
                })
            }
        }

        function initDatabaseLineage(aSelector, aNodes, aLinks) {
            this.setOptions({
                nodeWith: 80,
                nodeMinHeight: 80,
                textLayout: TEXT_LAYOUT_TYPE.RIGHT
            });
            initBaseLineage(aSelector, aNodes, aLinks);
            recomputeDatabaseNodesPosition(nodes.filter(function (node) {
                if (node.targetLinks.length == 0 && node.type == "database") return true;
                else return false;
            }));
            // recomputeXYPositionForGroup(nodes.filter(function (node) {
            // if (node.type == "database") return true;
            // else return false;
            // }));
            hideTooltip();
            // model for choose table.
            addNodeEvent("dblclick", ajaxTableLineageList);
            return this;
        }

        function recomputeXYPositionForGroup(sortNodes) {
            var yCount = 10;
            var xCount = 10;
            var interval = (WIDTH + MARGIN.LEFT + MARGIN.RIGHT) / (sortNodes.length + 1);
            sortNodes.forEach(function (node) {
                if (node.width > interval && yCount != 10) yCount = 10;
                else yCount = 50 + node.height;

                node.x = xCount;
                node.y = yCount;

                xCount += interval;
            });
        }

        function recomputeDatabaseNodesPosition(dbNode) {
            var x = 100, y = 100, intervalX = 300, intervalY = 120;

            function next(n, px, py) {
                n.sourceLinks.forEach(function (d) {
                    var target = d.target;

                    if (!target.ready && target.type == "database") {
                        target.x = px;
                        target.y = py;
                        target.ready = true;
                        next(target, px + intervalX, py);
                        py += intervalY;
                    }
                })
            }

            dbNode.forEach(function (n) {
                n.x = x;
                n.y = y;
                n.ready = true;
                next(n, x + intervalX, y);
                y += intervalY;
            });
        }

        function initTableLineage(aSelector, tableId, aNodes, aLinks) {
            this.setOptions({nodeWith: 36, textLayout: TEXT_LAYOUT_TYPE.BOTTOM});

            var centerNodes = findTableAndColumnNodes(tableId, aNodes);
            var nodeMap = d3.map(aNodes, function (d) {
                return d.id;
            });
            var sourceLinkMap = d3.map(d3.nest().key(function (d) {
                return d.source;
            }).entries(aLinks), function (d) {
                return d.key;
            });
            var targetLinkMap = d3.map(d3.nest().key(function (d) {
                return d.target;
            }).entries(aLinks), function (d) {
                return d.key;
            });
            var newLinkMap = d3.map();
            var newNodeMap = d3.map();

            function putNodeAndLinkIntoMap(d) {
                if (!newNodeMap.has(d.source)) {
                    var source = nodeMap.get(d.source);
                    newNodeMap.set(d.source, source);
                    if (source.parent != undefined)
                        !newNodeMap.has(source.parent) && newNodeMap.set(source.parent, nodeMap.get(source.parent));
                }
                if (!newNodeMap.has(d.target)) {
                    var target = nodeMap.get(d.target);
                    newNodeMap.set(d.target, target);
                    if (target.parent != undefined)
                        !newNodeMap.has(target.parent) && newNodeMap.set(target.parent, nodeMap.get(target.parent));
                }
                var newLinkId = d.source + "-" + d.target;
                !newLinkMap.has(newLinkId) && newLinkMap.set(newLinkId, d);
            }

            function findCascadeSource(node) {
                if (node == undefined) return;
                sourceLinkMap.has(node.id) && sourceLinkMap.get(node.id).values.forEach(function (d) {
                    putNodeAndLinkIntoMap(d);
                    findCascadeSource(nodeMap.get(d.target));
                })
            }

            function findCascadeTarget(node) {
                if (node == undefined) return;
                targetLinkMap.has(node.id) && targetLinkMap.get(node.id).values.forEach(function (d) {
                    putNodeAndLinkIntoMap(d);
                    findCascadeTarget(nodeMap.get(d.source));
                })
            }

            centerNodes.forEach(function (d) {
                !newNodeMap.has(d.id) && newNodeMap.set(d.id, d);
                findCascadeSource(d);
                findCascadeTarget(d);
            });

            initBaseLineage(aSelector, newNodeMap.values(), newLinkMap.values());

            var tables = nodes.filter(isTableNode);
            recomputeXYPositionForGroup(tables);

            return this;
        }

        function initBaseLineage(aSelector, aNodes, aLinks) {
            if (aNodes == undefined) return;

            initBase(aSelector, aNodes, aLinks);
            initSvgAndG();
            initTooltip();
            initDefs();
            initJointAndPath();

            disableUserInteractions(2 * TRANSITION_DURATION);
            return this;
        }

        function addNodeEvent(name, callback) {
            nodeEvents.push({name: name, callback: callback});
        }

        this.initTableLineage = initTableLineage;
        this.initDatabaseLineage = initDatabaseLineage;
        this.addNodeEvent = addNodeEvent;
        this.update = update;

        return this;
    }

    var lineageFactory = {};

    function createLineage() {
        return new Lineage();
    }

    lineageFactory.createLineage = createLineage;

    return lineageFactory;
});